Smart Memory Leak Detector
Chris Dragan

By definition, a memory leak appears when you subsequently allocate memory for certain purposes, but accidentaly you don't free some parts of it. As a result, the memory usage slowly grows up, because the memory allocator (malloc function in particular) has no clue which parts you aren't using anymore. This behavior doesn't have to be obvious at once, and it is usually not easy to overcome it.

The easiest way to avoid memory leaks is to write programs in a way that avoids them. We are not talking here about languages in which the structure of underlying memory allocators takes care of this problem (for instance Java). With the advent of modern C++ the programmer is provided with powerful language constructs that enable many transparent idioms that handle allocation and freeing of memory. Yet the language is flexible, still encouraging unaware programmer of using unsafe but useful syntax.

To make the program virtually "unleakable" it is enough to stick to only one rule: never delete. If you don't use the delete operator and leave the freeing of memory to appropriate objects, you can't forget about freeing, so the memory has no means to leak out. The same practice can be performed when using the malloc()/free() scheme instead of new/delete.

The simplest facility for automatic freeing of memory is auto_ptr from the standard C++ library.

    #include <memory>
    using namespace std;
    int main()
    {
        auto_ptr<MyClass> p_object( new MyClass );
        p_object->FancyFunction();
        return 0;
    }

The above example shows how to use auto_ptr. It behaves exactly the same way as regular pointer; the only difference is the assignment operator and copy constructor, which you should thoroughly study before using auto_ptr. The big advantage of auto_ptr is that its destructor automatically frees allocated memory, so no matter how many returns and throws has your function - the memory is always freed on time.

It is worth to mention here, that the auto_ptr should not be used with array new operator, like this:

    // Strongly discouraged
    auto_ptr<char> p_array( new char[ 100 ] );

For this purpose you have to create your own auto_ptr especially dedicated to handling arrays. A better solution in this case is to use C++ standard library's vector.

There are many different useful types of safe pointers like this. Another curious one is reference counted pointer. This type of pointer ensures that the object that it points to lives as long as the last pointer to it exists. After the last pointer to the object dies, the object's memory is automatically released.

    class ref_count_traits {
        int count;
    public:
        ref_count_traits(): count( 0 ) { }
        void Acquire() { count++; }
        void Release() { if ( --count <= 0 ) delete this; }
    protected:
        ref_count_traits( const ref_count& rc ): count( 0 ) { }
        ref_count_traits& operator=( const ref_count_traits& rc ) {
            count = 0;
            return *this;
        }
    };

    template <class T>
    class rc_ptr {
    public:
        typedef T* pointer;
        typedef T& reference;
    private:
        pointer p;
    public:
        rc_ptr(): p( 0 ) { }
        rc_ptr( pointer p ): p( p ) { Acquire(); }
        rc_ptr( const rc_ptr& p ): p( p.p ) { Acquire(); }
        ~rc_ptr() { Release(); }
        rc_ptr& operator=( pointer new_p ) {
            if ( new_p != p ) {
                Release();
                p = new_p;
            }
            Acquire();
            return *this;
        }
        rc_ptr& operator=( const rc_ptr& new_p ) {
            if ( new_p.p != p ) {
                Release();
                p = new_p.p;
                Acquire();
            }
            return *this;
        }
        reference operator*() const { return *p; }
        pointer operator->() const { return p; }
        operator reference() const { return *p; }
        operator pointer() const { return p; }
        bool operator==( pointer p2 ) const { return p == p2; }
        bool operator!=( pointer p2 ) const { return p != p2; }
        bool operator==( const rc_ptr& p2 ) const { return p == p2.p; }
        bool operator!=( const rc_ptr& p2 ) const { return p != p2.p; }
    private:
        void Acquire() { if ( p ) p->Acquire(); }
        void Release() { if ( p ) p->Release, p = 0; }
    };

In the above example, class ref_count_traits is an example class that can be used with the rc_ptr template reference counter pointer. One can either derive from ref_count_traits or create another class that has Acquire()/Release() function pair.

The safe pointers like the example one above prove to be very useful, but sometimes we don't use them because we can't or we just don't want to. It may also sometimes happen that we have a bug in a safe pointer and we still get a memory leak. What then?

Imagine that you can immediately detect all memory leaks in your program, locate and remove them. You've build an enormous application with the highest level of complexity possible, counting millions and millions of lines of source code. Nevertheless quick detection, location and removal of memory leaks is still possible.

If you could log all new and delete operator invocations, you could filter them and find only those new invocations that do not have a pairing delete.

C and C++ provide two very useful macros: __FILE__ and __LINE__. The first one is a const char* and is a string to the current source file's name. The other one is the current line number in the source file. We can use them to log memory allocation locations.

The first step to build a flexible memory leak detector is to define a simple macro:

#ifdef USE_MEMORY_LEAK_DETECTOR
    #define NEW new( __FILE__, __LINE__ )
#else
    #define NEW new
#endif

Obviously now you have to use your favorite file manager, and replace all "new" to "NEW" in all of your source files, provided that you include the header with this macro in all of them. This way the location of all memory locations will be logged.

Next step is to declare your own new and delete operators. The trick is to have a function new operator, that is called by the NEW macro with source file and line to log.

    inline void* operator new( size_t size )
    {
        return _LogAlloc( size, 0, 0 );
    }

    inline void* operator new( size_t size, const char* file, int line )
    {
        return _LogAlloc( size, file, line );
    }

    inline void operator delete( void* p )
    {
        _LogFree( p );
    }

The regular new operator is required to log all memory allocations - for the places where you don't replace "new" with "NEW" (like in the standard C++ library).

The final step is to define _LogAlloc() and _LogFree() functions and to create a simple program that later analyses the created log. After analysing the log you get all places in which you allocate memory which is never freed. Of course the main assumption is that your program terminates successfuly, otherwise you get false memory leaks. You know exactly where the memory that you allocate should be freed, so you can fix your program quickly.

The following links contain two files with a leak detector that you have to include with your source files and one with a command-line log analyser.
leak_detector.h
leak_detector.cpp
leak_analyser.cpp

The above leak detector is meant for use both with C and C++-style allocations (new/delete and malloc/free).

Chris Dragan